package loader

import (
	"os"
	"sort"
	"path/filepath"
	"kumachan/interpreter/lang/textual/ast"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/loader/manifest"
)


type Module struct {
	Name      string
	Path      string
	AST       *ast.Root
	ImpMap    map[string] *Module
	FileInfo  os.FileInfo
	Manifest  manifest.Module
}
type Index (map[string] *Module)
func makeIndexEmpty() Index {
	return make(Index)
}
func makeIndexWithStdlib() Index {
	var idx = make(Index)
	loadStdlib(&idx)
	return idx
}
func loadEntry(path string, fs FileSystem) (*Module, Index, *Error) {
	var ctx = makeEntryContext()
	var idx = makeIndexWithStdlib()
	var mod, err = loadModule(path, fs, ctx, idx)
	return mod, idx, err
}
func loadEntryThunk(t ModuleThunk, fs FileSystem) (*Module, Index, *Error) {
	var ctx = makeEntryContext()
	var idx = makeIndexWithStdlib()
	var mod, _, err = buildHierarchy(t, fs, ctx, idx)
	return mod, idx, err
}
func loadEntryWithoutStdlib(path string, fs FileSystem) (*Module, Index, *Error) {
	var ctx = makeEntryContext()
	var idx = makeIndexEmpty()
	var mod, err = loadModule(path, fs, ctx, idx)
	return mod, idx, err
}
func loadModule(path string, fs FileSystem, ctx context, idx Index) (*Module, *Error) {
	// Try to read the content of given source file/folder
	var is_project_root = (len(ctx.BreadCrumbs) == 0)
	var mod_thunk, err1 = readModulePath(path, fs, is_project_root)
	if err1 != nil { return nil, &Error {
		Context:  ctx,
		Concrete: E_ReadModuleFailed {
			FilePath: path,
			Message:  err1.Error(),
		},
	} }
	var mod, _, err2 = buildHierarchy(mod_thunk, fs, ctx, idx)
	if err2 != nil { return nil, err2 }
	return mod, nil
}

func LoadEntry(path string) (*Module, Index, *Error) {
	var abs_path, e = entryPathToAbsPath(path)
	if e != nil { return nil, nil, e }
	return loadEntry(abs_path, RealFileSystem {})
}
func LoadEntryThunk(raw_mod ModuleThunk) (*Module, Index, *Error) {
	return loadEntryThunk(raw_mod, RealFileSystem {})
}
func LoadEntryAllowEmpty(path string) (*Module, Index, *Error) {
	if path != "" {
		return LoadEntry(path)
	} else {
		return LoadEntryThunk(CraftEmptyThunk(manifest.Module {
			UniqueId: EmptyRootModuleId,
		}, "."))
	}
}

func entryPathToAbsPath(path string) (string, *Error) {
	var abs_path, err = filepath.Abs(path)
	if err != nil { return "", &Error {
		Context: makeEntryContext(),
		Concrete: E_ReadModuleFailed {
			FilePath: path,
			Message:  "cannot get absolute path of the given file",
		},
	} }
	return abs_path, nil
}
func (mod *Module) ForEachImported(f func(*Module)(source.Errors)) source.Errors {
	var names = make([] string, 0, len(mod.ImpMap))
	for preferred_name, _ := range mod.ImpMap {
		names = append(names, preferred_name)
	}
	sort.Strings(names)
	var errs source.Errors
	for _, preferred_name := range names {
		source.ErrorsJoinAll(&errs, f(mod.ImpMap[preferred_name]))
	}
	return errs
}


